package com.useastore.tea;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
 * <p>A Java implementation of TEA encryption algorithm.</p>
 * @author Michael
 * @version 0.0.1
 */
public class Tea {
    private long[] encrypt(long[] v, byte[] k) {
        if (v.length != 2 || k.length != 16) {
            return null;
        }
        long v0 = v[0];
        long v1 = v[1];
        long sum = 0;
        long i;
        long delta = 0x9e3779b9L;
        long adjust = 0x00000000FFFFFFFFL;

        long[] key = new long[4];
        long[] result = new long[2];
        for (int id=0; id<16; id=id+4) {
            key[id/4] = k[id]<<24|k[id+1]<<16|k[id+2]<<8|k[id+3];
        }
        for (i=0; i < 32; i++) {
            sum += delta;
            v0 += ((v1<<4) + key[0]) ^ (v1 + sum) ^ ((v1>>>5) + key[1]);
            v0 &= adjust;
            v1 += ((v0<<4) + key[2]) ^ (v0 + sum) ^ ((v0>>>5) + key[3]);
            v1 &= adjust;
        }
        result[0] = v0;
        result[1] = v1;
        return result;
    }
    
    private long[] decrypt(long[] v, byte[] k) {
        if (v.length != 2 || k.length != 16) {
            return null;
        }
        long v0 = v[0];
        long v1 = v[1];
        long i;
        long delta = 0x9e3779b9L;
        long sum = delta<<5;
        long adjust = 0x00000000FFFFFFFFL;
        
        long[] key = new long[4];
        long[] result = new long[2];

        for (int id=0; id<16; id=id+4) {
            key[id/4] = k[id]<<24|k[id+1]<<16|k[id+2]<<8|k[id+3];
        }
        for (i=0; i<32; i++) {
            v1 -= ((v0<<4) + key[2]) ^ (v0 + sum) ^ ((v0>>>5) + key[3]);
            v1 &= adjust;
            v0 -= ((v1<<4) + key[0]) ^ (v1 + sum) ^ ((v1>>>5) + key[1]);
            v0 &= adjust;
            sum -= delta;
        }
        result[0] = v0;
        result[1] = v1;
        return result;
    }
    
    public void encryptFile(String srcFile, String destFile, String key) {
        if (!isValid(srcFile) || !isValid(destFile) || !isValidKey(key)) {
            return;
        }
        
        FileInputStream fis = null;
        FileOutputStream fos = null;
        
        try {
            fis = new FileInputStream(srcFile);
            fos = new FileOutputStream(destFile);
            byte[] buf = new byte[8];
            byte[] k = key.getBytes();
            int len;
            while ((len=fis.read(buf)) != -1) {
                if (len == buf.length) {
                    long[] v = byteToLong(buf);
                    long[] result = encrypt(v, k);
                    buf = longToByte(result);
                }
                fos.write(buf, 0, len);
            }
        } catch (FileNotFoundException e) {
        } catch (IOException e) {
        }finally{
            try {
                if (null != fis) {
                    fis.close();
                }
                if (null != fos) {
                    fos.flush();
                    fos.close();
                }
            } catch (IOException e) {
                
            }
        }
    }
    
    public void decryptFile(String srcFile, String destFile, String key) {
        if (!isValid(srcFile) || !isValid(destFile) || !isValidKey(key)) {
            return;
        }
        
        FileInputStream fis = null;
        FileOutputStream fos = null;
        
        try {
            fis = new FileInputStream(srcFile);
            fos = new FileOutputStream(destFile);
            byte[] buf = new byte[8];
            byte[] k = key.getBytes();
            int len;
            while ((len=fis.read(buf)) != -1) {
                if (len == buf.length) {
                    long[] v = byteToLong(buf);
                    long[] result = decrypt(v, k);
                    buf = longToByte(result);
                }
                fos.write(buf, 0, len);
            }
        } catch (FileNotFoundException e) {
        } catch (IOException e) {
        }finally{
            try {
                if (null != fis) {
                    fis.close();
                }
                if (null != fos) {
                    fos.flush();
                    fos.close();
                }
            } catch (IOException e) {
                
            }
        }
    }
    
    private long[] byteToLong(byte[] b) {
        long[] r = new long[2];
        
        long[] bt = new long[4];
        bt[0] = 0xFF000000L;
        bt[1] = 0x00FF0000L;
        bt[2] = 0x0000FF00L;
        bt[3] = 0x000000FFL;
        
        r[0] = ((b[0]<<24)&bt[0])|((b[1]<<16)&bt[1])|((b[2]<<8)&bt[2])|(b[3]&bt[3]);
        r[1] = ((b[4]<<24)&bt[0])|((b[5]<<16)&bt[1])|((b[6]<<8)&bt[2])|(b[7]&bt[3]);
        return r;
    }
    
    private byte[] longToByte(long[] v) {
        long b = 0xFFL;
        
        byte[] r = new byte[8];
        long v1 = v[0];
        long v2 = v[1];
        
        r[0] = (byte)(v1>>>24&b);
        r[1] = (byte)(v1>>>16&b);
        r[2] = (byte)(v1>>>8&b);
        r[3] = (byte)(v1&b);
        r[4] = (byte)(v2>>>24&b);
        r[5] = (byte)(v2>>>16&b);
        r[6] = (byte)(v2>>>8&b);
        r[7] = (byte)(v2&b);
        return r;
    }
    
    private boolean isValid(String str) {
        boolean result = true;
        if (null == str || str.isEmpty()) {
            result = false;
        }
        return result;
    }
    
    private boolean isValidKey(String key) {
        boolean result = true;
        if (!isValid(key)) {
            result = false;
        } else if (key.length() != 16) {
            result = false;
        }
        return result;
    }
            
    public static void main(String[] args) {
        final String key = "abcdefghijklmnop";
        Tea tea = new Tea();
        tea.encryptFile("test.jpg", "teste.jpg", key);
        tea.decryptFile("teste.jpg", "tested.jpg", key);
    }

}
